package com.wzf.greattruth.dubbo.tester;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * DefaultFuture.
 * 
 * @author qian.lei
 * @author chao.liuc
 */
public class Future {
	private final long id;
	
	private static final Map<Long, Future> FUTURES = new ConcurrentHashMap<Long, Future>();

	private final Request request;

	private final int timeout;

	private final Lock lock = new ReentrantLock();

	private final Condition done = lock.newCondition();

	private volatile Response response;

	private static final int DEFAULT_TIMEOUT = 30 * 1000;

	public Future(Request request, int timeout) {
		this.request = request;
		this.id = request.getId();
		this.timeout = timeout > 0 ? timeout : DEFAULT_TIMEOUT;
		FUTURES.put(id, this);
	}

	public Object get() throws Exception {
		return get(timeout);
	}

	public Object get(int timeout) throws Exception {
		if (timeout <= 0) {
			timeout = DEFAULT_TIMEOUT;
		}
		if (!isDone()) {
			long start = System.currentTimeMillis();
			lock.lock();
			try {
				while (!isDone()) {
					done.await(timeout, TimeUnit.MILLISECONDS);
					if (isDone() || System.currentTimeMillis() - start > timeout) {
						break;
					}
				}
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			} finally {
				lock.unlock();
			}
			if (!isDone()) {
				throw new Exception("timeout");
			}
		}
		return returnFromResponse();
	}

	public void cancel() {
		Response errorResult = new Response();
		errorResult.setId(id);
		errorResult.setSuccess(false);
		response = errorResult;
		FUTURES.remove(id);
	}

	public boolean isDone() {
		return response != null;
	}

	private Object returnFromResponse() {
		Response res = response;
		if (res == null) {
			throw new IllegalStateException("response cannot be null");
		}
		return response.getMessage();
	}

	public Request getRequest() {
		return request;
	}

	public static Future getFuture(long id) {
		return FUTURES.get(id);
	}

	public static void received(Response response) {
		Future future = FUTURES.remove(response.getId());
		if (future != null) {
			future.doReceived(response);
		} else {
			System.err.println("The timeout response finally returned at "
					+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) + " id=" + response.getId()
					+ "  message=" + response.getMessage());
		}
	}

	private void doReceived(Response res) {
		lock.lock();
		try {
			response = res;
			if (done != null) {
				done.signal();
			}
		} finally {
			lock.unlock();
		}
	}

}